/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.coders;

import java.nio.ByteBuffer;

import org.latestbit.smartobjects.RequestContext;
import org.latestbit.smartobjects.exceptions.*;
import org.latestbit.smartobjects.io.ResizableByteBuffer;
import org.latestbit.smartobjects.network.INetworkCommand;
import org.latestbit.smartobjects.utils.ByteTools;

public class StdNetworkCommand implements INetworkCommand {
	private final byte PROTO_IDENT = (byte)0xAA; 
	public static final byte PROTOCOL_VERSION = 0x10;
	public static final int COMMAND_SIZE_POSITION = 8;
	public static final int GRAMMAR_SIZE_POSITION = 12;
	public static final int HEADER_SIZE = 16;
	
	enum SectionFlags {		
		NONE (0);

		private int value;
		
		SectionFlags(int value) {
			this.value = value;
		}
		
		public int getValue() {
			return this.value;
		}
	}	
	
	private byte protoVersion = PROTOCOL_VERSION;
	private byte commandId;
	private byte sectionsFlags = (byte)SectionFlags.NONE.getValue();
	private int commandBodySize = 0;
	private String destination = "";
	private String instanceId;
	private int messageId;
	private RequestContext requestContext;
	
	protected ResizableByteBuffer buffer;
	protected IDataTypesCoder coder;
	private boolean serialized = false;
	private boolean deserialized = false;
	
	
	public StdNetworkCommand(byte commandId, ResizableByteBuffer writeBuffer, IDataTypesCoder coder) {
		this.commandId = commandId;
		this.buffer = writeBuffer;
		this.coder = coder;
	}
	
	public StdNetworkCommand(ByteBuffer readBuffer, IDataTypesCoder coder) {
		this.buffer = new ResizableByteBuffer(readBuffer);
		this.coder = coder;
	}

	public StdNetworkCommand(INetworkCommand command) {
		this.buffer = command.getBuffer();
		this.commandId = command.getCommandId();
		this.destination = command.getDestination();
		this.instanceId = command.getInstanceId();
		this.messageId = command.getMessageId();
		this.protoVersion = command.getProtocolVersion();
		this.requestContext = command.getRequestContext();
		this.coder = command.getDataTypesCoder();
	}
	
	public void deserialize() throws SmartBusSystemException {
		this.coder.reset();
		decodeHeader();
		ByteBuffer readBuffer = buffer.getBuffer();
		
		if(isRequest()) {
			this.destination = coder.decodeString(readBuffer);
			this.instanceId = coder.decodeString(readBuffer);
			if(!this.coder.decodeNull(readBuffer)) {
				this.requestContext = new RequestContext();
				this.requestContext.deserialize(this.coder, null, readBuffer);
			}
			else
				this.requestContext = null;
		}
		this.deserialized = true;
	}

	private void decodeHeader() throws DataTypesCoderException {
		ByteBuffer readBuffer = buffer.getBuffer();
		byte protoIdent = readBuffer.get();
		if(protoIdent!=PROTO_IDENT) {
			System.err.println(readBuffer.toString());
			readBuffer.position ( readBuffer.position()-1 );
			byte[] packetData = new byte [ readBuffer.limit() ];
			readBuffer.get(packetData);
			throw new DataTypesCoderException("Incorrect protocol header received! \n"+ByteTools.byteArrayToHexString ( packetData ));			
		}
		protoVersion = readBuffer.get();
		if( (protoVersion & (byte)0xF0) != (PROTOCOL_VERSION & (byte)0xF0) )
			throw new DataTypesCoderException("Unsupported protocol version detected!");
		this.commandId = readBuffer.get();
		this.sectionsFlags = readBuffer.get();
		this.messageId = readBuffer.getInt();
		this.commandBodySize = readBuffer.getInt();
		
		// Grammar
		int grammarPosition = readBuffer.getInt();
		int saveCurrentPos = readBuffer.position();
		readBuffer.position(readBuffer.position()+grammarPosition);
		this.coder.decodeGrammar(readBuffer);
		readBuffer.position(saveCurrentPos);
	}

	public void beginSerialize() throws SmartBusSystemException {
		this.coder.reset();
		encodeHeader();
		
		if(isRequest()) {
			coder.encodeString(destination, buffer);
			coder.encodeString(instanceId, buffer);
			if(this.requestContext!=null)
				requestContext.serialize(this.coder, null, this.buffer);
			else
				this.coder.encodeNull( this.buffer );			
		}				
	}

	private void encodeHeader() throws DataTypesCoderException {
		buffer.put( PROTO_IDENT );
		buffer.put( PROTOCOL_VERSION);
		buffer.put(this.commandId);
		buffer.put(this.sectionsFlags);
		buffer.putInt(messageId);
		// Reserve field for size of packet
		buffer.putInt(0);
		// Reserve field for position of grammar
		buffer.putInt(0);
	}
	
	public void finishSerialize() throws SmartBusSystemException {
		int grammarPosition = buffer.getBuffer().position() - HEADER_SIZE;
		buffer.putInt(GRAMMAR_SIZE_POSITION, grammarPosition);
		this.getDataTypesCoder().encodeGrammar(buffer);
		
		this.commandBodySize = buffer.getBuffer().position() - HEADER_SIZE;
		if(this.commandBodySize<0)
			throw new DataTypesCoderException("Unable to finish serialization with body size < 0");
		buffer.putInt(COMMAND_SIZE_POSITION, commandBodySize);
		
		buffer.getBuffer().flip();		
		this.serialized = true;
	}	
	
	public int getHeaderSize() {
		return HEADER_SIZE;
	}
	
	public int getCommandBodySize() {
		return this.commandBodySize;
	}

	@Override
	public byte getCommandId() {
		return commandId;
	}
	
	@Override
	public byte getProtocolVersion() {
		return protoVersion;
	}

	public void setDestination(String destination) {
		this.destination = destination;
	}

	@Override
	public String getDestination() {
		return destination;
	}

	public void setInstanceId(String instanceId) {
		this.instanceId = instanceId;
	}

	@Override
	public String getInstanceId() {
		return instanceId;
	}

	@Override
	public ResizableByteBuffer getBuffer() {
		return buffer;
	}
	
	@Override
	public void setBuffer ( ResizableByteBuffer buffer ) {
		this.buffer = buffer;
	}
	
	public IDataTypesCoder getCoder() {
		return coder;
	}

	public void setMessageId(int messageId) {
		this.messageId = messageId;
	}

	@Override
	public int getMessageId() {
		return messageId;
	}
	
	public void setRequestContext(RequestContext requestContext) {
		this.requestContext = requestContext;
	}

	@Override
	public RequestContext getRequestContext() {
		return requestContext;
	}	

	@Override
	public final boolean isRequest() {
		return this.getCommandId() % 2 == 0;
	}
	
	@Override
	public final boolean isResponse() {
		return !isRequest();
	}

	public void setSerialized(boolean serialized) {
		this.serialized = serialized;
	}

	@Override
	public boolean isSerialized() {
		return serialized;
	}

	public void setDeserialized(boolean deserialized) {
		this.deserialized = deserialized;
	}

	@Override
	public boolean isDeserialized() {
		return deserialized;
	}
	
	@Override
	public IDataTypesCoder getDataTypesCoder() {
		return coder;
	}	
}
